package com.yh.vo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * 生成带有折叠功能的导航条分页信息（导航条集合中额外存放"..."表示折叠）
 * 导航按钮生成的规则通过构造函数指定，如未指定将使用默认值：
 *
 * @author yuhuan
 * @Param firstNextNum 首页导航段的范围将默认设置成"0"，表示除第一页外.不需要额外显示
 * @Param currentPrevPageNum 当前页前半段导航段的范围将默认设置成"2"，表示除当前页外，需要额外显示2个导航页
 * @Param currentNextPageNum 当前页后半段导航段的范围将默认设置成3，表示除当前页外，需要额外显示3个导航页
 * @Param lastPrevNum 尾页导航段的范围将默认设置成"0"，表示除尾页外，不需要额外显示
 * @date 2022/01/13
 */
public class CustomPageAndNavigate<T> implements Serializable {
    /**
     * 默认的总记录数
     * 如果结果列表选择自行赋值，总的记录数也需要自行确认，如果没有传递将使用默认的总记录数计算导航条
     */
    public static final int DEFAULT_TOTAL = 100;
    /**
     * 默认的首页导航段范围
     */
    public static final int DEFAULT_FIRST_NEXT_NUM = 0;
    /**
     * 默认的尾页导航段范围
     */
    public static final int DEFAULT_LAST_PREV_NUM = 0;
    /**
     * 默认的当前页前半段导航段的范围
     */
    public static final int DEFAULT_CURRENT_PREV_NUM = 2;
    /**
     * 默认的当前页后半段导航段的范围
     */
    public static final int DEFAULT_CURRENT_NEXT_NUM = 3;

    /**
     * 折叠导航条（该导航条增加了"..."代表折叠效果，由createEllipsisNavigate初始化）
     */
    private List<String> navigatepageEllipsisNums = new ArrayList<String>();
    /**
     * 导航条（存放计算出来所有需要显示的导航按钮，由createNavigatepageNums初始化）
     */
    private List<Integer> navigatepageNums = new ArrayList();
    /**
     * 当前页导航段的第一页
     */
    private int navigateFirstPage;
    /**
     * 当前页导航段的最后一页
     */
    private int navigateLastPage;

    /**
     * 分页参数 - 总记录数
     */
    private int total;
    /**
     * 分页参数 - 每页的记录数
     */
    private int pageSize;
    /**
     * 分页参数 - 当前页
     */
    private int pageNum;

    /**
     * 总页数
     */
    private int pages;
    /**
     * 当前页前半段导航段的范围（当前页前面需要显示的按钮数）
     */
    private int currentPrevPageNum;
    /**
     * 当前页后半段导航段的范围（当前页后面需要显示的按钮数）
     */
    private int currentNextPageNum;
    /**
     * 上一页
     */
    private int prePage;
    /**
     * 下一页
     */
    private int nextPage;

    /**
     * 数据或结果列表（数据结果需要自行通过方法封装）
     */
    private List<T> list;

    /**
     * 增附功能参数 修改首末页的导航按钮数
     * firstNextNum 首页导航段的范围（首页后面需要显示的按钮数）
     * firstNextPage 首页导航段的最后一页
     * lastPrevNum 尾页导航段的范围（尾页前面需要显示的按钮数）
     * lastPrevPage 尾页导航段的第一页
     */
    private int firstNextNum;
    private int firstNextPage;
    private int lastPrevNum;
    private int lastPrevPage;

    /**
     * 根据当前页和页面大小进行初始化，其余参数使用默认值
     *
     * @Param pageNum 当前页
     * @Param pageSize 页大小
     */
    public CustomPageAndNavigate(int pageNum, int pageSize) {
        this(pageNum, pageSize, DEFAULT_TOTAL);
    }

    /**
     * 根据当前页/页面大小/总记录数进行初始化，其余参数使用默认值
     *
     * @Param pageNum 当前页
     * @Param pageSize 页大小
     * @Param total 总记录数,可以选择是否封装总的记录数；不封装时，使用默认值初始化导航条（一般代指查询结果的总条数）
     */
    public CustomPageAndNavigate(int pageNum, int pageSize, int total) {
        this(DEFAULT_FIRST_NEXT_NUM, DEFAULT_CURRENT_PREV_NUM, DEFAULT_CURRENT_NEXT_NUM, DEFAULT_LAST_PREV_NUM, pageNum, pageSize, total, null);
    }

    /**
     * 根据当前页/页面大小/数据集进行初始化，将会自动筛选出当前页的数据集或者结果集
     *
     * @Param pageNum 当前页
     * @Param pageSize 页大小
     * @Param list 搜索到的所有的数据结果集
     */
    public CustomPageAndNavigate(int pageNum, int pageSize, List<T> list) {
        this(DEFAULT_FIRST_NEXT_NUM, DEFAULT_CURRENT_PREV_NUM, DEFAULT_CURRENT_NEXT_NUM, DEFAULT_LAST_PREV_NUM, pageNum, pageSize, list);
    }

    /**
     * 根据当前页/页面大小/数据集进行初始化，并自由调整导航条的规则
     *
     * @Param firstNextNum 首页导航范围
     * @Param currentPrevPageNum 当前页前半部分的导航范围
     * @Param currentNextPageNum 当前页后半部分的导航范围
     * @Param lastPrevNum 尾页导航范围
     * @Param pageNum 当前页
     * @Param pageSize 页大小
     * @Param list 搜索到的所有的数据结果集
     */
    public CustomPageAndNavigate(
            int firstNextNum,
            int currentPrevPageNum,
            int currentNextPageNum,
            int lastPrevNum,
            int pageNum, int pageSize, List<T> list) {
        this(firstNextNum, currentPrevPageNum, currentNextPageNum, lastPrevNum, pageNum, pageSize, list.size(), list);
    }

    /**
     * 根据当前页/页面大小初始化，并自由调整导航条的规则
     *
     * @Param firstNextNum 首页导航范围
     * @Param currentPrevPageNum 当前页前半部分的导航范围
     * @Param currentNextPageNum 当前页后半部分的导航范围
     * @Param lastPrevNum 尾页导航范围
     * @Param pageNum 当前页
     * @Param pageSize 页大小
     */
    public CustomPageAndNavigate(
            int firstNextNum,
            int currentPrevPageNum,
            int currentNextPageNum,
            int lastPrevNum,
            int pageNum, int pageSize) {
        this(firstNextNum, currentPrevPageNum, currentNextPageNum, lastPrevNum, pageNum, pageSize, DEFAULT_TOTAL, null);
    }

    /**
     * 根据当前页/页面大小/总记录数初始化，并自由调整导航条的规则
     *
     * @Param firstNextNum 首页导航范围
     * @Param currentPrevPageNum 当前页前半部分的导航范围
     * @Param currentNextPageNum 当前页后半部分的导航范围
     * @Param lastPrevNum 尾页导航范围
     * @Param pageNum 当前页
     * @Param pageSize 页大小
     * @Param total 总记录数
     */
    public CustomPageAndNavigate(
            int firstNextNum,
            int currentPrevPageNum,
            int currentNextPageNum,
            int lastPrevNum,
            int pageNum, int pageSize, int total, List<T> list) {
        this.firstNextNum = firstNextNum;
        this.currentPrevPageNum = currentPrevPageNum;
        this.currentNextPageNum = currentNextPageNum;
        this.lastPrevNum = lastPrevNum;
        this.total = total;
        this.pageSize = pageSize;
        // ceil()向上取整
        pages = (int) Math.ceil(total * 1.0 / pageSize);
        /* 优化当前页的范围，保证当前页始终在（1-总页数）之间 */
        if (pageNum < 1) {
            pageNum = 1;
        } else if (pageNum > pages) {
            pageNum = pages;
        }
        this.pageNum = pageNum;
        // 分页数据以及导航条的初始化
        pageinto();
        createNavigatepageNums();
        if (list != null && list.size() > 0) {
            createList(list);
        }
    }

    /**
     * 初始化分页参数
     */
    private void pageinto() {
        /** 初始化上一页 */
        prePage = Math.max(pageNum - 1, 1);
        /** 初始化下一页 */
        nextPage = Math.min(pageNum + 1, pages);
        /** 初始化首页导航条的最后一页 */
        firstNextPage = 1 + firstNextNum;
        /** 初始化尾页导航条的第一页 */
        lastPrevPage = pages - lastPrevNum;

        /* 动态调整页码范围，使显示出来的页码按钮的数量始终保持不变 */
        // 如果当前页的导航条按钮数超过了当前页约定的第一页的极限范围则做以下调整
        if (pageNum - currentPrevPageNum <= firstNextPage) {
            // 将前半部分多出的按钮数量追加到后半部分
            currentNextPageNum += firstNextPage - (pageNum - currentPrevPageNum) + 1;
        }
        // 如果当前页的导航条按钮数超过了当前页约定的最后一页的极限范围则做以下调整
        if (pageNum + currentNextPageNum >= lastPrevPage) {
            // 将后半部分多出的按钮数量追加到前半部分
            currentPrevPageNum += (pageNum + currentNextPageNum) - lastPrevPage + 1;
        }
    }

    /**
     * 使用Stream流计算当前分页需要显示的所有页码
     */
    private void createNavigatepageNums() {
        // 首页的页码按钮显示范围
        IntStream firstNextPagNums = IntStream.rangeClosed(0, firstNextPage);
        // 尾页的页码按钮显示范围
        IntStream lastPrevPageNums = IntStream.rangeClosed(lastPrevPage, pages);
        // 当前页前面页码按钮的显示范围
        IntStream currentPrevPageNums = IntStream.rangeClosed(pageNum - currentPrevPageNum, pageNum);
        // 初始化当前页导航条的第一页
        navigateFirstPage = Math.max(pageNum - currentPrevPageNum, 1);
        // 当前页后面页码按钮的显示范围
        IntStream currentNextPageNums = IntStream.rangeClosed(pageNum, pageNum + currentNextPageNum);
        // 初始化当前页导航条的最后一页
        navigateLastPage = Math.min(pageNum + currentNextPageNum, pages);

        /* 将所有计算出来的页码范围合并，并过滤掉不符合或多余的页码按钮 */
        Stream.of(firstNextPagNums, lastPrevPageNums, currentPrevPageNums, currentNextPageNums)
                // 定义流中流并将所有的字符串转为int
                .flatMapToInt(intStream -> intStream)
                // 越界筛选，筛选出规定范围内的页码按钮(合法范围1-pages)
                .filter(e -> e >= 1 && e <= pages)
                // 去除重复的页码按钮
                .distinct()
                // 将筛选出来的页码按自然顺序排序
                .sorted()
                // 将排序好的页码依次放入到导航容器
                .forEach(e -> navigatepageNums.add(e));
        // 初始化具有折叠功能的导航条
        createEllipsisNavigate();
    }

    /**
     * 插入'...'折叠按钮
     */
    private List<String> createEllipsisNavigate() {
        for (int i = 0; i < navigatepageNums.size(); i++) {
            // 如果集合的最大索引范围满足集合最大长度时才能触发
            if (i + 1 < navigatepageNums.size()) {
                // 如果当前索引的导航按钮值与下一个索引的导航值相差大于2时触发，添加折叠效果
                if (navigatepageNums.get(i) < navigatepageNums.get(i + 1) - 1) {
                    // 修复首页导航缺失的问题，如果在首页导航范围之内
                    if (i <= firstNextPage) {
                        navigatepageEllipsisNums.add(String.valueOf(navigatepageNums.get(i)));
                    }
                    /* 细节优化，如果当前索引的按钮是当前页后半部分的最后一页则添加到导航条
                       如果该导航按钮在导航条集合中已经存在，将不会添加（解决当前页前后段的范围都设为"0"会出现两个当前页的问题）*/
                    if (navigatepageNums.get(i) == pageNum + currentNextPageNum &&
                            !navigatepageEllipsisNums.contains(String.valueOf(navigatepageNums.get(i)))) {
                        navigatepageEllipsisNums.add(String.valueOf(navigatepageNums.get(i)));
                    }
                    navigatepageEllipsisNums.add("...");
                } else {
                    navigatepageEllipsisNums.add(String.valueOf(navigatepageNums.get(i)));
                }
            } else {
                navigatepageEllipsisNums.add(String.valueOf(navigatepageNums.get(i)));
            }
        }
        return navigatepageEllipsisNums;
    }

    /**
     * 初始化当前页的结果集
     */
    private void createList(List<T> list) {
        this.list = new ArrayList<T>();
        // 计算当前页数据的起始索引
        int startIndex = (this.pageNum - 1) * this.pageSize;
        // 计算当前页数据的结束索引
        int endIndex = ((this.pageNum - 1) * this.pageSize + this.pageSize) - 1;
        // 如果当前页就是最后一页，结束索引就等于总记录数减1
        if (this.pageNum == this.pages) {
            endIndex = this.total - 1;
        }
        // 初始化当前页的数据集
        for (int index = startIndex; index <= endIndex; index++) {
            this.list.add(list.get(index));
        }
    }

    public void setList(List<T> list) {
        this.list = list;
    }

    public List<String> getNavigatepageEllipsisNums() {
        return navigatepageEllipsisNums;
    }

    public List<Integer> getNavigatepageNums() {
        return navigatepageNums;
    }

    public int getNavigateFirstPage() {
        return navigateFirstPage;
    }

    public int getNavigateLastPage() {
        return navigateLastPage;
    }

    public int getTotal() {
        return total;
    }

    public int getPageSize() {
        return pageSize;
    }

    public int getPageNum() {
        return pageNum;
    }

    public int getPages() {
        return pages;
    }

    public int getCurrentPrevPageNum() {
        return currentPrevPageNum;
    }

    public int getCurrentNextPageNum() {
        return currentNextPageNum;
    }

    public int getPrePage() {
        return prePage;
    }

    public int getNextPage() {
        return nextPage;
    }

    public List<T> getList() {
        return list;
    }

    public int getFirstNextNum() {
        return firstNextNum;
    }

    public int getFirstNextPage() {
        return firstNextPage;
    }

    public int getLastPrevNum() {
        return lastPrevNum;
    }

    public int getLastPrevPage() {
        return lastPrevPage;
    }
}
